package tech.lq0.dreamaticvoyage.block.entity;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.*;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import org.jetbrains.annotations.Nullable;
import tech.lq0.dreamaticvoyage.capability.ModCapabilities;
import tech.lq0.dreamaticvoyage.capability.uce.UCEnergyStorage;
import tech.lq0.dreamaticvoyage.gui.menu.FukamizuReactingChamberMenu;
import tech.lq0.dreamaticvoyage.gui.slot.ContainerEnergyData;
import tech.lq0.dreamaticvoyage.init.BlockEntityRegistry;
import tech.lq0.dreamaticvoyage.recipe.FukamizuReactingRecipe;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

public class FukamizuReactionChamberBlockEntity extends BlockEntity implements WorldlyContainer, MenuProvider {

    protected static final int SLOT_INPUT = 0;
    protected static final int SLOT_UP = 1;
    protected static final int SLOT_LEFT = 2;
    protected static final int SLOT_DOWN = 3;
    protected static final int SLOT_RIGHT = 4;
    protected static final int SLOT_RESULT = 5;

    private static final int[] SLOTS_FOR_UP = new int[]{SLOT_INPUT};
    private static final int[] SLOTS_FOR_NORTH = new int[]{SLOT_DOWN};
    private static final int[] SLOTS_FOR_SOUTH = new int[]{SLOT_UP};
    private static final int[] SLOTS_FOR_WEST = new int[]{SLOT_RIGHT};
    private static final int[] SLOTS_FOR_EAST = new int[]{SLOT_LEFT};
    private static final int[] SLOTS_FOR_DOWN = new int[]{SLOT_RESULT};

    public static final int DEFAULT_PROCESS_TIME = 200;
    public static final int MAX_DATA_COUNT = 3;
    public static final int MAX_ENERGY = 6400;
    public static final int DEFAULT_ENERGY_COST = 16;

    public int progress;
    public int currentMaxProgress;

    protected NonNullList<ItemStack> items = NonNullList.withSize(6, ItemStack.EMPTY);

    private LazyOptional<UCEnergyStorage> energyHandler;
    private LazyOptional<?>[] itemHandlers = SidedInvWrapper.create(this, Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST);

    protected final ContainerEnergyData dataAccess = new ContainerEnergyData() {
        public long get(int pIndex) {
            return switch (pIndex) {
                case 0 -> {
                    AtomicInteger energy = new AtomicInteger();
                    FukamizuReactionChamberBlockEntity.this.getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(consumer -> energy.set(consumer.getEnergyStored()));
                    yield energy.get();
                }
                case 1 -> FukamizuReactionChamberBlockEntity.this.progress;
                case 2 -> FukamizuReactionChamberBlockEntity.this.currentMaxProgress;
                default -> 0;
            };
        }

        public void set(int pIndex, long pValue) {
            switch (pIndex) {
                case 0:
                    FukamizuReactionChamberBlockEntity.this.getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(consumer -> consumer.receiveEnergy((int) pValue, false));
                    break;
                case 1:
                    FukamizuReactionChamberBlockEntity.this.progress = (int) pValue;
                    break;
                case 2:
                    FukamizuReactionChamberBlockEntity.this.currentMaxProgress = (int) pValue;
                    break;
            }
        }

        public int getCount() {
            return MAX_DATA_COUNT;
        }
    };

    public FukamizuReactionChamberBlockEntity(BlockPos pPos, BlockState pBlockState) {
        super(BlockEntityRegistry.FUKAMIZU_REACTION_CHAMBER_BLOCK_ENTITY.get(), pPos, pBlockState);

        this.energyHandler = LazyOptional.of(() -> new UCEnergyStorage(MAX_ENERGY));
    }

    public static void serverTick(Level level, BlockPos pos, BlockState blockState, FukamizuReactionChamberBlockEntity blockEntity) {
        AtomicInteger energy = new AtomicInteger(0);
        blockEntity.getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(handler -> energy.set(handler.getEnergyStored()));
        if (energy.get() < DEFAULT_ENERGY_COST) {
            return;
        }

        if (blockEntity.hasRecipe()) {
            blockEntity.getCurrentRecipe().ifPresent(recipe -> blockEntity.currentMaxProgress = recipe.tick);

            blockEntity.progress++;
            blockEntity.energyHandler.ifPresent(consumer -> consumer.extractEnergy(DEFAULT_ENERGY_COST, false));

            if (blockEntity.progress >= blockEntity.currentMaxProgress) {
                blockEntity.craftItem();
                blockEntity.resetProgress();
                blockEntity.setChanged();
            }
        } else {
            blockEntity.resetProgress();
        }
    }

    private void craftItem() {
        Optional<FukamizuReactingRecipe> recipe = getCurrentRecipe();
        if (recipe.isEmpty()) {
            return;
        }

        ItemStack result = recipe.get().getResultItem(null);

        ItemStack input = this.items.get(SLOT_INPUT);
        if (input.hasCraftingRemainingItem()) {
            this.items.set(SLOT_INPUT, input.getCraftingRemainingItem());
        } else {
            input.shrink(1);
        }

        checkIngredient(SLOT_UP);
        checkIngredient(SLOT_LEFT);
        checkIngredient(SLOT_DOWN);
        checkIngredient(SLOT_RIGHT);

        ItemStack output = this.items.get(SLOT_RESULT);
        this.items.set(SLOT_RESULT, new ItemStack(result.getItem(), output.getCount() + result.getCount()));
    }

    private void checkIngredient(int slot) {
        ItemStack item = this.items.get(slot);
        if (!item.isEmpty()) {
            if (item.hasCraftingRemainingItem()) {
                this.items.set(slot, item.getCraftingRemainingItem());
            } else {
                item.shrink(1);
            }
        }
    }

    private Optional<FukamizuReactingRecipe> getCurrentRecipe() {
        if (this.level == null) {
            return Optional.empty();
        }

        SimpleContainer inventory = new SimpleContainer(this.items.size());
        for (int i = 0; i < this.items.size(); i++) {
            inventory.setItem(i, this.items.get(i));
        }

        return this.level.getRecipeManager().getRecipeFor(FukamizuReactingRecipe.Type.INSTANCE, inventory, level);
    }

    private boolean hasRecipe() {
        Optional<FukamizuReactingRecipe> recipe = getCurrentRecipe();

        if (recipe.isEmpty()) {
            return false;
        }

        if (getLevel() == null) {
            return false;
        }

        ItemStack result = recipe.get().getResultItem(getLevel().registryAccess());

        return canInsertAmountIntoOutputSlot(result.getCount()) && canInsertItemIntoOutputSlot(result.getItem());
    }

    private boolean canInsertItemIntoOutputSlot(Item item) {
        return this.items.get(SLOT_RESULT).isEmpty() || this.items.get(SLOT_RESULT).is(item);
    }

    private boolean canInsertAmountIntoOutputSlot(int count) {
        return this.items.get(SLOT_RESULT).getCount() + count <= this.items.get(SLOT_RESULT).getMaxStackSize();
    }

    private void resetProgress() {
        this.progress = 0;
        this.currentMaxProgress = DEFAULT_PROCESS_TIME;
    }

    @Override
    public void load(CompoundTag pTag) {
        super.load(pTag);

        this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
        ContainerHelper.loadAllItems(pTag, this.items);
        if (pTag.contains("UmisuEnergy")) {
            getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(handler -> ((UCEnergyStorage) handler).deserializeNBT(pTag.get("UmisuEnergy")));
        }
        this.progress = pTag.getInt("Progress");
        this.currentMaxProgress = pTag.getInt("CurrentMaxProgress");
    }

    @Override
    protected void saveAdditional(CompoundTag pTag) {
        super.saveAdditional(pTag);

        ContainerHelper.saveAllItems(pTag, this.items);
        getCapability(ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY).ifPresent(handler -> pTag.put("UmisuEnergy", ((UCEnergyStorage) handler).serializeNBT()));
        pTag.putInt("Progress", this.progress);
        pTag.putInt("CurrentMaxProgress", this.currentMaxProgress);
    }

    @Override
    public int[] getSlotsForFace(Direction pSide) {
        return switch (pSide) {
            case UP -> SLOTS_FOR_UP;
            case NORTH -> SLOTS_FOR_NORTH;
            case SOUTH -> SLOTS_FOR_SOUTH;
            case WEST -> SLOTS_FOR_WEST;
            case EAST -> SLOTS_FOR_EAST;
            default -> SLOTS_FOR_DOWN;
        };
    }

    @Override
    public boolean canPlaceItemThroughFace(int pIndex, ItemStack pItemStack, @Nullable Direction pDirection) {
        return pDirection != Direction.DOWN && pIndex != SLOT_RESULT;
    }

    @Override
    public boolean canTakeItemThroughFace(int pIndex, ItemStack pStack, Direction pDirection) {
        return pDirection == Direction.DOWN && pIndex == SLOT_RESULT;
    }

    @Override
    public int getContainerSize() {
        return this.items.size();
    }

    @Override
    public boolean isEmpty() {
        for (ItemStack itemstack : this.items) {
            if (!itemstack.isEmpty()) {
                return false;
            }
        }

        return true;
    }

    @Override
    public ItemStack getItem(int pSlot) {
        return this.items.get(pSlot);
    }

    @Override
    public ItemStack removeItem(int pSlot, int pAmount) {
        return ContainerHelper.removeItem(this.items, pSlot, pAmount);
    }

    @Override
    public ItemStack removeItemNoUpdate(int pSlot) {
        return ContainerHelper.takeItem(this.items, pSlot);
    }

    @Override
    public void setItem(int pSlot, ItemStack pStack) {
        ItemStack itemstack = this.items.get(pSlot);
        boolean flag = !pStack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, pStack);
        this.items.set(pSlot, pStack);
        if (pStack.getCount() > this.getMaxStackSize()) {
            pStack.setCount(this.getMaxStackSize());
        }

        if (pSlot != SLOT_RESULT && !flag) {
            this.resetProgress();
            this.setChanged();
        }
    }

    @Override
    public boolean stillValid(Player pPlayer) {
        return Container.stillValidBlockEntity(this, pPlayer);
    }

    @Override
    public void clearContent() {
        this.items.clear();
    }

    @Override
    public Component getDisplayName() {
        return Component.translatable("container.dreamaticvoyage.fukamizu_reaction_chamber");
    }

    @Nullable
    @Override
    public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) {
        return new FukamizuReactingChamberMenu(pContainerId, pPlayerInventory, this, this.dataAccess);
    }

    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    @Override
    public CompoundTag getUpdateTag() {
        CompoundTag compoundtag = new CompoundTag();
        compoundtag.putInt("Progress", this.progress);
        compoundtag.putInt("CurrentMaxProgress", this.currentMaxProgress);
        return compoundtag;
    }

    @Override
    public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
        if (cap == ModCapabilities.UMISU_CURRENT_ENERGY_CAPABILITY) {
            return energyHandler.cast();
        }
        if (!this.remove && side != null && cap == ForgeCapabilities.ITEM_HANDLER) {
            return switch (side) {
                case UP -> itemHandlers[0].cast();
                case DOWN -> itemHandlers[1].cast();
                case NORTH -> itemHandlers[2].cast();
                case SOUTH -> itemHandlers[3].cast();
                case WEST -> itemHandlers[4].cast();
                case EAST -> itemHandlers[5].cast();
            };
        }
        return super.getCapability(cap, side);
    }

    @Override
    public void invalidateCaps() {
        super.invalidateCaps();
        for (LazyOptional<?> itemHandler : itemHandlers) itemHandler.invalidate();
        this.energyHandler.invalidate();
    }

    @Override
    public void reviveCaps() {
        super.reviveCaps();
        this.itemHandlers = SidedInvWrapper.create(this, Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST);
        this.energyHandler = LazyOptional.of(() -> new UCEnergyStorage(MAX_ENERGY));
    }
}
